package main

import "fmt"

func main() {
	src := make([]int, 0, 1024)
	for i := 0; i < 257; i++ {
		src = append(src, i)
	}
	res, value, _ := SliceDelete[int](src, 30)
	fmt.Println(len(res), cap(res), value)
}

// SliceDelete 大明老师的代码
// 删除后的切片、删除的元素、错误
// https://gitee.com/geektime-geekbang_admin/geektime-basic-go/blob/master/homework/week1/slice.go
func SliceDelete[T any](src []T, index int) ([]T, T, error) {
	length := len(src)
	// 边界判断
	if index < 0 || index >= length {
		var zero T // 创建类型空值
		return nil, zero, newErrorIndexOutOfRange(length, index)
	}
	value := src[index]
	// 从index位置，都往前挪一位
	copy(src[index:], src[index+1:])
	src = src[:length-1]
	// 缩容
	src = Shrink(src)
	return src, value, nil
}

// 下标越界
func newErrorIndexOutOfRange(length, index int) error {
	return fmt.Errorf("下标越界: 长度%d, 下标%d\n", length, index)
}

// Shrink 缩容
func Shrink[T any](src []T) []T {
	capacity, length := cap(src), len(src)
	capShrink, needChanged := calCapacity(capacity, length)
	if !needChanged { // 如果不需要缩容
		return src
	}
	res := make([]T, 0, capShrink)
	res = append(res, src...)
	return res
}

// 计算缩容后的容量
/*
整个实现的核心是希望在后续少触发扩容的前提下，一次性释放尽可能多的内存
缩容最关键的一个地方是计算缩容后的容量
比如说，你使用对半开的原则，原1024，如果我的元素个数只剩下512，
然后他家把容量缩减到512，这种做法不是说不行，
而是忽略了一个问题：你在删除以后，你可能还会有append操作，如果按照上面这样缩容，你下一次append又会扩容
因此设计缩容的时候要考虑到一个点，缩容后，即便用户还有append的操作，也不要触发扩容
即 你缩容的时候，给他留点余地。
如果切片本身容量就很小，例如64，这时候就犯不着缩容，即便cap=64,len=1，因为占用内存不会特别大，可以容忍
（不一定是64，你也可以还是128也可以是32，是一个经验值）
*/
func calCapacity(capacity, length int) (int, bool) {
	// 如果容量小于等于64，无需缩容
	if capacity <= 64 {
		return capacity, false
	}
	// 如果在 2048 以内，并且元素不足 1/4，那么直接缩减为一半
	// 例如：cap=1024, len=256, 缩小到 512, 留有空间, 避免缩容后立马扩容
	if capacity <= 2048 && (capacity/length) >= 4 {
		return capacity / 2, true
	}
	// 如果容量大于2048，进行一个0.625的缩容，也就是 5/8
	// 也就是比一半多一点，和正向扩容的 1.25 倍相呼应
	if capacity > 2048 && (capacity/length) >= 2 {
		factor := 0.625 // 缩容因子
		return int(float32(capacity) * float32(factor)), true
	}
	return capacity, false
}
